package com.tfswx.jkgl.core.sjcc.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.tfswx.jkgl.core.bean.JkglRuntimeData;
import com.tfswx.jkgl.core.bean.Mxgxdy;
import com.tfswx.jkgl.core.bean.Zddy;
import com.tfswx.jkgl.core.dao.SqlMapper;
import com.tfswx.jkgl.core.dto.CsdygxInputDTO;
import com.tfswx.jkgl.core.dto.SaveInputDTO;
import com.tfswx.jkgl.core.sjcc.DataSave;
import com.tfswx.jkgl.core.sjcc.ScclUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Smile
 * @date 2021/12/9
 */
@Slf4j
@Component
public class SjmxSaveImpl implements DataSave {
    @Resource
    private JkglRuntimeData jkglRuntimeData;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Object save(SaveInputDTO dto) {
        Object csdy = dto.getCsdy();
        String clcxcs = dto.getClcx().getClcxcs();
        List<CsdygxInputDTO> csgxs = dto.getCsgx();

        if (jkglRuntimeData.getMxgxList() == null || jkglRuntimeData.getZddyList() == null) {
            throw new RuntimeException("数据模型关系或实体字段定义未初始化");
        }

        //获取数据模型
        List<Mxgxdy> mxgxdyList = jkglRuntimeData.getMxgxList().getOrDefault(clcxcs, new ArrayList<>());
        if (mxgxdyList.isEmpty()) {
            throw new RuntimeException("未找到数据模型");
        }
        //解析表数据
        TableData tableData = saveData(null, csdy, csgxs, mxgxdyList);

        //入参是对象返回主键，入参是集合返回Boolean
        if (csdy instanceof Map) {
            return tableData == null ? "" : tableData.getMajorValue();
        }

        return true;
    }

    private TableData saveData(TableData parent, Object csdy, List<CsdygxInputDTO> csgxList, List<Mxgxdy> mxgxdyList) {
        TableData res = null;
        List<Map<String, Object>> param = new ArrayList<>();

        if (csdy instanceof Map) {
            param.add((Map<String, Object>) csdy);
        } else if (csdy instanceof List) {
            List<Object> tempList = (List<Object>) csdy;
            if (tempList.size() > 0) {
                Object o = tempList.get(0);
                if (o instanceof Map) {
                    param.addAll((List<Map<String, Object>>) csdy);
                }
            }
        }

        TableData tableData;
        CsdygxInputDTO csgx;
        TableData childrenTableData;
        Mxgxdy tableMode;
        Map<String, Map<String, Object>> tables;
        List<Zddy> tableFields;
        for (Map<String, Object> item : param) {
            tables = new HashMap();
            //解析每行数据
            for (Map.Entry<String, Object> cs : item.entrySet()) {
                if (cs.getValue() instanceof Map || cs.getValue() instanceof List) {
                    continue;
                }
                csgx = csgxList.stream().filter(x -> x.getCsbm().equals(cs.getKey())).findFirst().orElse(null);
                if (csgx == null) {
                    throw new RuntimeException(cs.getKey() + "未找到参数对应关系");
                }

                tables.computeIfAbsent(csgx.getStmc(), x -> new HashMap<>());

                tables.get(csgx.getStmc()).put(csgx.getZdmc(), cs.getValue());
            }

            //获取子表数据
            List<Map.Entry<String, Object>> childrenData = item.entrySet().stream().filter(x -> x.getValue() instanceof Map || x.getValue() instanceof List).collect(Collectors.toList());

            Set<Map.Entry<String, Map<String, Object>>> tableEntrySet = tables.entrySet();
            for (Map.Entry<String, Map<String, Object>> table : tableEntrySet) {
                String tableName = table.getKey();
                if (parent == null) {
                    tableMode = mxgxdyList.stream().filter(x -> mxgxdyList.get(0).getZst().equals(x.getZstmc())).findFirst().orElse(null);
                } else {
                    String zstMc = parent.getName();
                    tableMode = mxgxdyList.stream().filter(x -> x.getZstmc().equals(zstMc) && x.getCstmc().equals(tableName)).findFirst().get();
                }
                if (tableMode == null) {
                    throw new RuntimeException(StrUtil.format("表【{}】未找到数据模型", tableName));
                }

                tableFields = jkglRuntimeData.getZddyList().getOrDefault(tableName, new ArrayList<>());

                tableData = new TableData(tableName, tables.get(tableName), tableFields, tableMode.getGlgxsql());
                res = tableData;
                if (parent != null) {
                    tableData.setForeign(parent.getForeignData(tableMode.getGlgxsql()));
                }
                tableData.execute();

                //                parent = tableData;
                List<Mxgxdy> children = mxgxdyList.stream().filter(x -> x.getZstmc().equals(tableName)).collect(Collectors.toList());
                for (Mxgxdy sjmx : children) {
                    if (!tables.containsKey(sjmx.getCstmc())) {
                        continue;
                    }
                    tableFields = jkglRuntimeData.getZddyList().getOrDefault(tableName, new ArrayList<>());
                    childrenTableData = new TableData(sjmx.getCstmc(), tables.get(sjmx.getCstmc()), tableFields, "");

                    tableData.setForeign(parent.getForeignData(tableMode.getGlgxsql()));
                    childrenTableData.execute();
                }
                for (Map.Entry<String, Object> data : childrenData) {
                    saveData(tableData, data.getValue(), csgxList, mxgxdyList);
                }
            }
        }
        return res;
    }

    @Data
    public static class SqlDTO {
        private String sql;
        private Map<String, Object> param;
    }

    @Data
    class TableData {
        public TableData(String name, Map<String, Object> data, List<Zddy> fields, String relationship) {
            this.name = name;
            this.data = data;
            this.fields = fields;
            this.relationship = relationship;
            this.majorKey = fields.stream().filter(x -> "Y".equals(x.getSfzj())).findFirst().orElse(null);
        }

        /**
         * 表名称
         */
        private String name;

        /**
         * 表关系
         */
        private String relationship;

        /**
         * 主键字段
         */
        private Zddy majorKey;

        /**
         * 主键值
         */
        private Object majorValue;

        /**
         * 外键值
         */
        private Map<String, Object> foreignValue;

        /**
         * 表数据
         */
        private Map<String, Object> data;

        /**
         * 字段定义
         */
        private List<Zddy> fields;


        public void setForeign(Map<String, Object> data) {
            this.foreignValue = data;
        }

        /**
         * 获取外键数据
         *
         * @return
         */
        private Map<String, Object> getForeignData() {
            return getForeignData(this.relationship);
        }

        private Map<String, Object> getForeignData(String relationship) {
            Map<String, Object> res = new HashMap<>();
            if (StringUtils.isBlank(relationship)) {
                throw new RuntimeException("数据模型关联关系不能存在");
            }

            String[] relationships = relationship.split("AND");

            String[] tables;
            String field;
            Object value;
            for (String item : relationships) {
                if (item.contains(this.name)) {
                    tables = item.split("=");
                    if (tables.length != 2) {
                        throw new RuntimeException("数据模型关联关系格式不正确");
                    }

                    //获取主表字段数据
                    field = tables[0].substring(tables[0].lastIndexOf(".") + 1).trim();
                    value = data.get(field);

                    //获取从表字段数据
                    field = tables[1].substring(tables[1].lastIndexOf(".") + 1).trim();
                    res.put(field, value);
                }
            }
            return res;
        }

        public Object execute() {
            if (majorKey == null) {
                throw new RuntimeException(name + "：字段定义未设置主键");
            }
            SqlDTO sqlDTO;
            this.majorValue = this.data.get(majorKey.getZdmc());
            Map<String, String> data = null;

            if (this.majorValue != null && StringUtils.isNotBlank(this.majorValue.toString())) {
                data = this.select();
            }

            if (data == null) {
                sqlDTO = toInsertSql();
            } else {
                sqlDTO = toUpdateSql();
            }
            try (SqlMapper sqlMapper = new SqlMapper()) {
                sqlMapper.execute(sqlDTO.sql, sqlDTO.getParam());
            }
            return this.majorValue;
        }

        private Map<String, String> select() {
            String sql = "SELECT {} FROM {}{}";
            List<String> columns = new ArrayList<>();

            data.entrySet().forEach(x -> {
                if (!x.getKey().equals(majorKey.getZdmc())) {

                    columns.add(x.getKey());
                }
            });
            String selectField = StrUtil.join(",", data.keySet().toArray());
            //            columns.set(columns.get().replaceAll("(,)+$", ""));

            sql = StrUtil.format(sql, selectField, name, this.toWhere());
            log.info(sql);
            List<Map<String, String>> data;
            try (SqlMapper sqlMapper = new SqlMapper()) {
                data = (List<Map<String, String>>) sqlMapper.selectList(sql, null, null);
            }
            return data.isEmpty() ? null : data.get(0);
        }

        private SqlDTO toInsertSql() {
            String sql = StrUtil.format("INSERT INTO {} ({}) VALUES({})", name);
            List<String> columns = new ArrayList<>();
            List<Object> values = new ArrayList<>();
            //获取存在生成策略字段
            List<Zddy> collect = fields.stream().filter(x -> StringUtils.isNotBlank(x.getScclbm())).collect(Collectors.toList());

            if (this.foreignValue != null && StringUtils.isNotBlank(this.foreignValue.toString())) {
                this.data.putAll(this.foreignValue);
            }

            for (Zddy item : collect) {
                Object value = this.data.getOrDefault(item.getZdmc(), null);
                //入参中不包含字段或字段为空则添加默认值
                if (value == null) {
                    JSONObject param = null;
                    if (StrUtil.isNotBlank(item.getSccldtcs())) {
                        param = JSONObject.parseObject(item.getSccldtcs());
                        param.entrySet().forEach(x -> {
                            x.setValue(this.data.getOrDefault(x.getValue(), null));
                        });
                    } else if (StrUtil.isNotBlank(item.getScclbm())) {
                        param = JSONObject.parseObject(item.getScclcs());
                    }

                    value = ScclUtil.get(item.getScclbm(), param);
                    if ("Y".equals(item.getSfzj())) {
                        this.majorValue = value;
                    }
                    this.data.put(item.getZdmc(), value);
                }
            }

            Map<String, Object> param = new HashMap<>();

            data.forEach((key, value) -> {
                columns.add(key);
                values.add("#{" + key + "}");
                param.put(key, value);
            });

            sql = StrUtil.format(sql, this.join(columns, ","), this.join(values, ","));

            SqlDTO sqlDTO = new SqlDTO();
            sqlDTO.setSql(sql);
            sqlDTO.setParam(param);
            log.info(sql);
            this.majorValue = this.data.get(majorKey.getZdmc());
            return sqlDTO;
        }

        private SqlDTO toUpdateSql() {
            //修改
            String sql = StrUtil.format("UPDATE {} SET {}{} ", name);

            List<String> columns = new ArrayList<>();
            Zddy lastDate = this.fields.stream().filter(x -> "ZHXGSJ".equals(x.getZdmc().toUpperCase())).findFirst().orElse(null);
            if (lastDate != null) {
                data.put(lastDate.getZdmc(), new Date());
            }

            Map<String, Object> param = new HashMap<>();

            data.entrySet().forEach(x -> {
                if (!x.getKey().equals(majorKey)) {
                    columns.add(x.getKey() + " = #{" + x.getKey() + "}");
                    param.put(x.getKey(), x.getValue());
                }
            });

            sql = StrUtil.format(sql, this.join(columns, ","), this.toWhere());

            SqlDTO sqlDTO=new SqlDTO();
            sqlDTO.setSql(sql);
            sqlDTO.setParam(param);

            log.info(sql);
            return sqlDTO;
        }

        private String toWhere() {
            String res = "";
            //修改
            String sql = " WHERE {}";

            List<String> whereList = new ArrayList<>();
            fields.stream().filter(x -> "0000010001".equals(x.getZdlbbm())).forEach(x -> {
                Object value = null;
                if (foreignValue != null) {
                    value = foreignValue.getOrDefault(x.getZdmc(), null);
                }
                if (value == null) {
                    value = data.getOrDefault(x.getZdmc(), null);
                }

                whereList.add(StrUtil.format("{} = {}", x.getZdmc(), toValueFormat(value)));
            });
            String where = this.join(whereList, " AND ");
            if (StrUtil.isNotBlank(where)) {
                res = StrUtil.format(sql, where);
            }
            return res;
        }

        private Object toValueFormat(Object value) {
            Object res;

            if (value == null) {
                res = "null";
            } else if (value instanceof String) {
                res = StrUtil.format("'{}'", value.toString().replace("'", "''"));
            } else if (value instanceof Date) {
                res = StrUtil.format("'{}'", DateUtil.format((Date) value, "yyyy-MM-dd HH:mm:ss"));
            } else {
                res = value;
            }
            return res;
        }

        public String join(List list, String separator) {
            StringBuilder str = new StringBuilder();
            for (int i = 0; i < list.size(); i++) {
                str.append(list.get(i));
                if ((i + 1) < list.size()) {
                    str.append(separator);
                }
            }
            return str.toString();
        }
    }
}
